#pragma once

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>

#include "log.hpp"
#include "protocol.hpp"
using namespace std;

enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    LISREN_ERR,
    OPEN_ERR,
};

static const uint16_t gport = 10086; // 默认端口号，外部没传就调用这个
static const int gbacklog = 5;

// req是输入型参数，res是输出型参数
typedef function<bool(const Request &req, Response &res)> func_t; // 功能为，req这个请求的响应结果放到了res中

// 正常来说HandlerEnter的解耦方式，是回调函数比较好一些，
// 但是这里为了简单的演示，将函数放到类外来简单实现解耦
void HandlerEnter(int sockfd, func_t func)
{
    string inbuffer;
    while (true)
    {
        // 1.读取————读取到的是序列字节流（报文）
        // 这里先要解决一个问题————这里怎么保证读到的是一份完整的请求呢

        string str_text; //用于接收一个完整的请求
        if (!recvPackage(sockfd, inbuffer, &str_text)) // 由于是多进程执行，因此recvRequest可以选择阻塞式读取
            return;
        cout << "带报头的请求：\n" << str_text << endl; 
        // 接收到一个完整报文之后，就要将有效载荷分离出来
        string req_text; //用于接受请求的有效载荷，即"x op y"
        if (!deLength(str_text, &req_text))
            return;
        cout << "去掉报头的正文：\n" << req_text << endl; 
        
        // 2.对请求(Request)进行反序列化
        // 反序列化后得到的是一个结构化的数据。
        Request req;
        if (!req.deserialize(req_text)) // 反序列化
            return;                   // 反序列化失败就返回
        // cout << req._x << req._op << req._y << endl;
        // 3.对结构化数据进行处理 (只有3这里才是真正的业务逻辑)
        // 这里的处理是根据规定好的业务协议去处理的。
        // 处理后会得到一个结构化的响应(Response)。
        Response res;
        func(req, res); // 这里有一个设计， req是输入型参数，res是输出型参数，res最终会被修改，即响应的结果
        // cout << res._exitcode << res._result << endl;
        // 走到这里，res已经填充好处理响应后的数据了

        // 4.将响应后的结构化数据进行序列化
        // 得到一个序列字节流（即报文)
        string res_str;
        res.serialize(res_str);
        cout << "计算完成，将响应的结构化数据进行序列化: " << res_str << endl;

        // 5.发送响应————将序列字节流（报文）发送给客户端
        // 先构建成完整的报文之后才能发出去
        // 完成这五步才算是一个完整的业务逻辑处理过程
        string send_string;
        if(!enLength(send_string, res_str))
            return;

        cout << "添加报头，构建完整的响应报文: \n" << send_string << endl; 
        // 接下来就是发送，write可以，send也可以
        send(sockfd, send_string.c_str(), send_string.size(), 0); // 最后一个参数是发送方式，默认为0
        // 注意了其实这里的send还是有问题的
    }
}

class tcpServer
{
public:
    tcpServer(const uint16_t &port = gport)
        : _listensockfd(-1), _port(port)
    {}

    void InitServer()
    {
        // 1.创建tcp套接字(socket)
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0); // SOCK_STREAM表示要创建TCP套接字
        if (_listensockfd < 0)
        {
            logMessage(FATAL, "create socket error");
            exit(SOCKET_ERR);
        }
        logMessage(NORMAL, "create socket success: %d", _listensockfd);

        // 2.bind 绑定自己的网络信息【ip和port等】
        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY; // 任意地址bind
        if (bind(_listensockfd, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "bind error");
            exit(BIND_ERR);
        }
        logMessage(NORMAL, "bind success");

        // 由于tcp是需要建立连接的，因此这里需要设置一个监听
        // 3. 设置socket为监听状态【需要知道客户端是否给我发消息】
        if (listen(_listensockfd, gbacklog) < 0)
        {
            logMessage(FATAL, "listen socket error");
            exit(LISREN_ERR);
        }
        logMessage(NORMAL, "listen socket success");
    }

    void Start(func_t func) // 在服务器启动的时候，告诉服务器要执行func这个函数（任务）
    {
        for (;;)
        {
            // tcp不能直接接受或发送消息，需要先建立链接
            // 1.server获取新链接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensockfd, (struct sockaddr *)&peer, &len);
            if (sockfd < 0)
            {
                logMessage(ERROR, "accept error, next"); // 获取新链接失败不会导致程序崩溃，再获取就行了
                continue;
            }
            logMessage(NORMAL, "accept a new link success! new sock: %d", sockfd);

            // 由于tcp的通信逻辑在读取数据的时候就是一个死循环，因此当有多个客户端进来的时候，就无法为多个客户端提供服务
            // 为了解决这个问题，就需要设计出多个解决方案【多进程，多线程， 线程池，多路转接】
            // 有关其他版本的代码这里就不贴了，忘记或者想再看的话去tcp_1的tcpServer.hpp这个文件中去看

            // 2.2多进程版本2

            // 弄成非阻塞等待，但是要结合信号捕捉，当子进程退出的时候，捕捉其信号再让父进程去捕捉它、【signal(SIGCHLD, SIG_IGN)】
            signal(SIGCHLD, SIG_IGN); // SIG_IGN是LinuxOS提供的一个专门用来处理子进程资源释放的函数
            pid_t id = fork();
            if (id == 0)
            {
                close(_listensockfd);
                // serverIO(sockfd);
                // 为了体现应用层的业务中，数据是如何处理发送和接收的，这里来一个不用serverIO这个业务逻辑，用一个简单的计算器
                HandlerEnter(sockfd, func);
                // 为了让这个tcpServer类更加的纯粹，只负责服务器任务，这里的HandlerEnter不能是类内方法，要进行解耦————即之前学习过的回调函数来处理
                close(sockfd);
                exit(1);
            }
            // 不需要父进程等待子进程了
            close(sockfd); // 文件描述符必须关掉，因为不关的话会导致文件描述符泄漏
        }
    }

    ~tcpServer() {}

private:
    int _listensockfd; // listen套接字【不进行数据通信，只是用来监听是否有链接到来，建立新链接】
    uint16_t _port;
    string _ip;
};